package javapop.framework;

public abstract class Parser<T> {
	private static int genId = 1;
	
	public String id;
	private ParseBuilder<T> builder;
	
	public Parser() {
		id = "parser"+(genId++);
		builder = new DefaultParseBuilder<T>();
	}
	
	public abstract boolean isMemoized();
	
	protected ParseResult<T> buildResult(T value) {
		return builder.build(value);
	}
		
	public void setBuilder(ParseBuilder<T> builder) {
		this.builder = builder;
	}
	
	public Parser<T> getParser() {
		return this;  // trick used for references
	}
	
	public final ParseResult<T> parse(ParseInput input) {
		return parse(new DefaultParseContext(),input);
	}
	
	public final ParseResult<T> parse(ParseContext<?> ctx, ParseInput input) {
		if(ctx==null) {
			throw new IllegalArgumentException("Parse context should not be <null>");
		}
		if(input==null) {
			throw new IllegalArgumentException("Parse input should not be <null>");
		}
		
		// skip before
		boolean skipMore;
		do {
			skipMore = false;
			for(Parser<?> skipParser : ctx.getSkipParsers()) {
				int savePos = input.getPos();
				if(!skipParser.parse(input).isError() && input.getPos()>savePos) {
					skipMore = true;
				}
			}
		} while(skipMore);
		
		Parser<T> parser = getParser();
		
		if(parser.isMemoized()) {
			ParseResult<T> memo = null;
			memo = (ParseResult<T>) ctx.recall(parser,input);
			if(memo!=null) {
				memo.checkTypeWithExplanations();
				input.move(memo.getEnd().getPos());
				return memo;
			}
			// no memo found
			ParseResult error = ctx.isFail(parser,input);
			if(error!=null) {
				return error;
			}
			// no error found
		}
		ParsePosition start = input.getParsePosition();
		ParseResult<T> result = (ParseResult<T>) eval(ctx,input);
		result.checkTypeWithExplanations();
		result.setStart(start);
		result.setEnd(input.getParsePosition());
		if(parser.isMemoized()) {
			if(!result.isError()) {
				// success
				ctx.memo(parser,start.getPos(),input,(ParseResult<?>) result);
			} else {
				// failure
				ctx.fail(parser,start.getPos(),input,(ParseError) result);		
			}
		}
		if(result.isError()) {
			input.move(start.getPos());
		}

		// skip after
		do {
			skipMore = false;
			for(Parser<?> skipParser : ctx.getSkipParsers()) {
				int savePos = input.getPos();
				if(!skipParser.parse(input).isError() && input.getPos()>savePos) {
					skipMore = true;
				}
			}
		} while(skipMore);

		
		return result;
	}
	
	public abstract String getDescription();
	
	public abstract ParseResult<?> eval(ParseContext<?> ctx, ParseInput input);
	
	@Override
	public int hashCode() {
		return id.hashCode();
	}
	
	@Override
	public boolean equals(Object obj) {
		if(obj==null) return false;
		return obj==this;  // referential equality
	}
	
	@Override
	public final String toString() {
		return getDescription();
	}
}
